package com.example.array;

import java.util.HashMap;
import java.util.Map;

/**
 * 有效数字（按顺序）可以分成以下几个部分：
 *  一个 小数 或者 整数
 *  （可选）一个 'e' 或 'E' ，后面跟着一个 整数
 *  小数（按顺序）可以分成以下几个部分：
 *  （可选）一个符号字符（'+' 或 '-'）
 *  下述格式之一：
 *  至少一位数字，后面跟着一个点 '.'
 *  至少一位数字，后面跟着一个点 '.' ，后面再跟着至少一位数字
 *  一个点 '.' ，后面跟着至少一位数字
 *
 *  整数（按顺序）可以分成以下几个部分：
 *  （可选）一个符号字符（'+' 或 '-'）
 *  至少一位数字
 *
 *  部分有效数字列举如下：
 *  ["2", "0089", "-0.1", "+3.14", "4.", "-.9", "2e10", "-90E3", "3e+7", "+6e-1","53.5e93", "-123.456e789"]
 *
 *  部分无效数字列举如下：
 *  ["abc", "1a", "1e", "e3", "99e2.5", "--6", "-+3", "95a54e53"]
 *
 *
 *  给你一个字符串 s ，如果 s 是一个 有效数字 ，请返回 true 。
 *
 *  示例 1：
 * 输入：s = "0"
 * 输出：true
 *
 *  示例 2：
 * 输入：s = "e"
 * 输出：false
 *
 *  示例 3：
 * 输入：s = "."
 * 输出：false
 *
 *  示例 4：
 * 输入：s = ".1"
 * 输出：true
 */
public class Leetcode65_IsNumber {
    public static void main(String[] args) {
        String s = ".";
        s = "e";
        s = ".1";
        s = "0";
        s = "+6e-1";
        String[] strs = {"2", "0089", "-0.1", "+3.14", "4.", "-.9", "2e10", "-90E3", "3e+7", "+6e-1","53.5e93", "-123.456e789"};
        String[] strs2 = {"abc", "1a", "1e", "e3", "99e2.5", "--6", "-+3", "95a54e53"};
        for (String str : strs) {
            System.out.println(str);
            System.out.println(new Solution().isNumber(str));
            System.out.println("============================");
        }
    }

    static class Solution {
        enum CharType{
            CHAR_NUMBER,
            CHAR_EXP,
            CHAR_POINT,
            CHAR_SIGN,
            CHAR_ILLEGAL
        }

        enum State {
            STATE_INITIAL,                     // 初始状态
            STATE_INT_SIGN,                    // 符号位
            STATE_INTEGER,                     // 整数部分(可作为结束状态)
            STATE_POINT,                       // 小数点(可作为结束状态)
            STATE_POINT_WITHOUT_INT,           // 左侧无整数的小数点(可作为结束状态)
            STATE_FRACTION,                    // 小数部分(可作为结束状态)
            STATE_EXP,                         // 字符 e
            STATE_EXP_SIGN,                    // 指数部分的符号位
            STATE_EXP_NUMBER,                  // 指数部分的整数部分(可作为结束状态)
            STATE_END                          //
        }
        /**
         * 解法三:有限状态自动机
         * https://leetcode-cn.com/problems/valid-number/solution/you-xiao-shu-zi-by-leetcode-solution-298l/
         *
         * @param s
         * @return
         */
        private boolean isNumber3(String s) {
            Map<CharType, State> initialMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_INTEGER);
                put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT);
                put(CharType.CHAR_SIGN, State.STATE_INT_SIGN);
            }};

            // 定义各个状态可能的转化关系
            Map<State, Map<CharType, State>> transfer = new HashMap<>();
            transfer.put(State.STATE_INITIAL, initialMap); // 初始化可作为初始状态的状态集合
            Map<CharType, State> intSignMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_INTEGER);
                put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT);
            }};
            transfer.put(State.STATE_INT_SIGN, intSignMap);
            Map<CharType, State> integerMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_INTEGER);
                put(CharType.CHAR_EXP, State.STATE_EXP);
                put(CharType.CHAR_POINT, State.STATE_POINT);
            }};
            transfer.put(State.STATE_INTEGER, integerMap);
            Map<CharType, State> pointMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_FRACTION);
                put(CharType.CHAR_EXP, State.STATE_EXP);
            }};
            transfer.put(State.STATE_POINT, pointMap);
            Map<CharType, State> pointWithoutIntMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_FRACTION);
            }};
            transfer.put(State.STATE_POINT_WITHOUT_INT, pointWithoutIntMap);
            Map<CharType, State> fractionMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_FRACTION);
                put(CharType.CHAR_EXP, State.STATE_EXP);
            }};
            transfer.put(State.STATE_FRACTION, fractionMap);
            Map<CharType, State> expMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER);
                put(CharType.CHAR_SIGN, State.STATE_EXP_SIGN);
            }};
            transfer.put(State.STATE_EXP, expMap);
            Map<CharType, State> expSignMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER);
            }};
            transfer.put(State.STATE_EXP_SIGN, expSignMap);
            Map<CharType, State> expNumberMap = new HashMap<CharType, State>() {{
                put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER);
            }};
            transfer.put(State.STATE_EXP_NUMBER, expNumberMap);

            int length = s.length();
            State state = State.STATE_INITIAL;

            for (int i = 0; i < length; i++) {
                CharType type = toCharType(s.charAt(i));
                if (!transfer.get(state).containsKey(type)) {
                    return false;
                } else {
                    state = transfer.get(state).get(type);
                }
            }
            return state == State.STATE_INTEGER
                    || state == State.STATE_POINT
                    || state == State.STATE_FRACTION
                    || state == State.STATE_EXP_NUMBER
                    || state == State.STATE_END;
        }

        private CharType toCharType(char ch) {
            if (ch >= '0' && ch <= '9') {
                return CharType.CHAR_NUMBER;
            } else if (ch == 'e' || ch == 'E') {
                return CharType.CHAR_EXP;
            } else if (ch == '.') {
                return CharType.CHAR_POINT;
            } else if (ch == '+' || ch == '-') {
                return CharType.CHAR_SIGN;
            } else {
                return CharType.CHAR_ILLEGAL;
            }
        }

        /**
         * 解法二:正则表达式
         * @param s
         * @return
         */
        public boolean isNumber2(String s) {
            return s.matches("[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[Ee][+-]?\\d+)?");
        }

        /**
         * 分情况讨论(未通过)
         * @param s
         * @return
         */
        public boolean isNumber1(String s) {
            char[] chars = s.toCharArray();
            int points = 0;
            int eSize = 0;

            // 首位字符只能是 0~9/./+/-
            if (!Character.isDigit(chars[0]) && chars[0] != '+' && chars[0] != '-' && chars[0] != '.') return false;
            if (chars[0] == 'e' || chars[0] == 'E') return false;
            if (chars.length == 1 && chars[0] == '.') return false;
            if (chars[0] == '.') ++points;
            for (int i = 1; i < chars.length; i++) {
//                System.out.println(i);
                char curChar = chars[i];
                if (!Character.isDigit(curChar)
                        && (curChar != '+' && curChar != '-' && curChar != '.') && (curChar != 'e' && curChar != 'E')) { // 不是 0-9/E/e/+/-/.
//                    System.out.println("xxxx");
                    return false;
                }

                if (curChar == '.') {
//                    System.out.println("mmmm");
                    if (++points >= 2) return false; // 多个 '.'
                    if (eSize >= 1) return false; // e/E后面数字串中有'.'
                    if (chars[i - 1] == 'e' || chars[i - 1] == 'E') return false;
                    if (i + 1 < chars.length && (chars[i + 1] == 'e' || chars[i + 1] == 'E')) return false;
                }

                if ((curChar == '+' || curChar == '-') && (chars[i - 1] != 'e' && chars[i - 1] != 'E')) { // 串中间有'+'/'-'且不是紧跟E/e之后的
//                    System.out.println("nnnn");
                    return false;
                }


                if (curChar == 'e' || curChar == 'E') {// 多个 'e'/'E'，或者'e'/'E'后面不接数字的
                    if (++eSize >= 2 || i == chars.length - 1) {
//                        System.out.println("kkkkk");
                        return false;
                    }

                    if (chars[i - 1] == '.') return false;

                    if (i + 1 == chars.length - 1 && (chars[i + 1] == '+' || chars[i + 1] == '-')) return false;
                }

            }
            return true;
        }

        public boolean isNumber(String s) {
            return isNumber3(s);
        }
    }
}
